一、循环语句与发汇编后的机器语言

1、for循环

下面是一段C语言的代码,我们的目的是来看其反汇编的结果:

int myfuncn(int a,int b)
{
    int c = a+b;
    int i;
    for(i=0;i<50;i++)
    {
        c = c+i;
    }
    return c;
}

前面的反汇编暂时不理它,这里从for的地方开始反汇编,结果如下:

for(i=0;i<50;i++)
00412BC7  mov  dword ptr [i],0           // i=0; 给循环变量赋初值
00412BCE  jmp  myfunction+39h (412BD9h)  // 跳到第一次循环处
00412BD0  mov  eax,dword ptr [i]
00412BD3  add  eax,1                     // i++;修改循环变量
00412BD6  mov  dword ptr [i],eax
00412BD9  cmp  dword ptr [i],32h         // 比较 i 与50的关系, 检查循环条件
00412BDD  jge  myfunction+4Ah (412BEAh)   // 当 i>=50 [即 !(i<50) ] 时则跳出循环
{
    c = c+i;
    00412BDF  mov  eax,dword ptr [c]      // 变量 c
    00412BE2  add  eax,dword ptr [i]      // 变量 i
    00412BE5  mov  dword ptr [c],eax      // c=c+i;
}
00412BE8  jmp  myfunction+30h (412BD0h)   // 跳回去修改循环变量
00412BEA  mov  eax,dword ptr [c]
}

可以看到for循环主要用这么几条指令来实现:mov进行初始化。jmp跳过循环变量改变代码。cmp实现条件判断,jge根据条件跳转。

用jmp回到循环改变代码进行下一次循环。所以for结构有以下的显著特征: 

mov <循环变量>,<初始值>   //给循环变量赋初值
jmp B      							 //跳到第一次循环处
A: (改动循环变量)                    //修改循环变量。
…
B: cmp <循环变量>,<限制变量>  //检查循环条件
jgp  跳出循环
(循环体)
…
jmp A                                //跳回去修改循环变量

2、do循环

再看一下do循环,因为 do循环没有修改循环变量的部分,所以比for循环要简单一些。

do
{
    c = c+i;
    00411A55  mov        eax,dword ptr [c]
    00411A58  add         eax,dword ptr [i]
    00411A5B  mov         dword ptr [c],eax
} while(c< 100);
00411A5E  cmp        dword ptr [c],64h
00411A62  jl           myfunction+35h (411A55h)
return c;

do循环就是一个简单的条件跳转回去。只有两条指令:

cmp <循环变量>,<限制变量>

jl <循环开始点>

3、while循环

while(c<100){
    00411A55  cmp         dword ptr [c],64h
    00411A59  jge         myfunction+46h (411A66h)
    c = c+i;
    00411A5B  mov         eax,dword ptr [c]
    00411A5E  add         eax,dword ptr [i]
    00411A61  mov         dword ptr [c],eax
}
00411A64  jmp         myfunction+35h (411A55h)
return c;

很明显,我们会发现while要更复杂一点。因为while除了开始的时候判断循环条件之外,后面还必须有一条无条件跳转回到循环开始的地方,共用三条指令实现:

A: cmp <循环变量>,<限制变量>
jge  B
( 循环体)
…
jmp A
B: (循环结束了)

这样,我们对C语言中的循环结构的分析,就基本弄完了!当然,我们用同样的方法也可以分析出C语言在“分支语句”、以及其它的数据结构中的C代码与机器反汇编代码的关系了!*转载请注明来自看雪论坛@PEdiy.com

 

二、分支语句

1、if-else 语句

为了观察其汇编语句,下面是一个简单的if判断结构:

if(a>0 && a<10)
{
    printf("a>0");
}
else if( a>10 && a<100)
{
    printf("a>10 && a<100");
}
else
{
    printf("a>10 && a<100");
}

if 判断都是使用cmp再加上条件跳转指令。对于if( A && B)的情况,一般都是使用否决法。如果A不成立,立刻跳下一个分支。依次,如果 B 不成立,同样跳下一分支。

cmp 条件

jle 下一个分支

所以开始部分的反汇编为:

if(a>0 && a<10)
00411A66  cmp       dword ptr [c],0
00411A6A  jle       411A81h   ; 跳下一个else if的判断点
00411A6C  cmp       dword ptr [c],0Ah
00411A70  jge       411A81h   ; 跳下一个else if的判断点
{
    printf("a>0");
    00411A72  push       offset string "a>0" (4240DCh)
    00411A77  call       @ILT+1300(_printf) (411519h)
    00411A7C  add        esp,4
}

else if 的和 else 的特点是,开始都有一条无条件跳转到判断结束处,阻止前面的分支执行结束后,直接进入这个分支。这个分支能执行到的唯一途径只是,前面的判断条件不满足。

else 则在jmp之后直接执行操作。而else if则开始重复if之后的操作,用cmp比较,然后用条件跳转指令时行跳转。

else if( a>10 && a<100)
00411A7F  jmp  411AA9h     ;直接跳到判断块外
00411A81  cmp  dword ptr [c],0Ah ;比较+条件跳转,目标为下一个分支处
00411A85  jle  411A9Ch
00411A87  cmp  dword ptr [c],64h
00411A8B  jge  411A9Ch
{
    printf("a>10 && a<100");
    00411A8D  push  offset string "a>10 && a<100" (424288h)
    00411A92  call  @ILT+1300(_printf) (411519h)
    00411A97  add  esp,4
}
else
    00411A9A  jmp  411AA9h   ;这里是else,所以只有简单的一条跳转。
{
    printf("a>10 && a<100");
    00411A9C  push  offset string "a>10 && a<100" (424288h)
    00411AA1  call  @ILT+1300(_printf) (411519h)
    00411AA6  add   esp,4
}
return c;

2、switch-case 语句

switch 的特点是有多个判断。因为 swtich 显然不用判断大于小于,所以都是je(因此,C语言中switch语句不支持float类型的变量),分别跳到每个case处。最后一个是无条件跳转,直接跳到default处。以下的代码:

switch(a)
{
    case 0:
    printf("a>0");
    case 1:
    {
        printf("a>10 && a<100");
        break;
    }
    default:
    printf("a>10 && a<100");
}

反汇编的switch(a)

00411A66  mov         eax,dword ptr [a]
00411A69  mov         dword ptr [ebp-0E8h],eax
00411A6F  cmp         dword ptr [ebp-0E8h],0  // case 0:
00411A76  je            411A83h
00411A78  cmp         dword ptr [ebp-0E8h],1  // case 1:
00411A7F  je            411A90h
00411A81  jmp         411A9Fh  // default:
{
    …

显然是比较a 是否是0、1这两个数字。汇编指令先把a移动到[ebp-0E8h]这个地址,然后再比较,这是调试版本编译的特点。可能是为了防止直接操作堆栈而导致堆栈破坏?最后一条直接跳转到default处。当然,如果没有default,就会跳到swtich{}之外。

从这里我们可以发现:switch语句里,完成“比较判断”的指令会与“case”指令的两部分,在汇编中,不是按照C语句逐句翻译的,而是分开为两个指令模块来实现的!

case 0:
printf("a>0");
00411A83  push        offset string "a>0"  (4240DCh)
00411A88  call          @ILT+1300(_printf) (411519h)
00411A8D  add         esp,4
case 1:
{
    printf("a>10 && a<100");
    00411A90  push        offset string "a>10 && a<100" (424288h)
    00411A95  call          @ILT+1300(_printf) (411519h)
    00411A9A  add         esp,4
    break;
    00411A9D  jmp         myfunction+8Ch (411AACh)
}
default:
printf("a>10 && a<100");
00411A9F  push        offset string "a>10 && c<100" (424288h)
00411AA4  call          @ILT+1300(_printf) (411519h)
00411AA9  add         esp,4
}

至于case 和 default分支中,如果有break,则会增加一个无条件跳转汇编指令。若没有break,则就没有任何循环控制代码。

小结:如果在反汇编代码中发现连续多个“比较cmp”和“相等跳转je”就会让人联想到“switch”语句了!